`default_nettype none
module decouple (
    input clk,
    input rst,

    input  i_valid,
    output i_ready,

    output o_valid,
    input  o_ready
);
  wire o_valid_set = i_valid & i_ready;
  wire o_valid_clr = o_valid & o_ready;

  reg_lr #(1) o_valid_r (
      .load(o_valid_set | o_valid_clr),
      .din (o_valid_set),
      .dout(o_valid),
      .*
  );
  assign i_ready = o_valid_clr | (~o_valid);
endmodule

module pipe_stage #(
    parameter DW = 32
) (
    input clk,
    input rst,
    input i_valid,
    output i_ready,
    input [DW-1:0] i_data,

    output o_valid,
    input o_ready,
    output [DW-1:0] o_data
);

  wire i_fire = i_valid & i_ready;
  decouple inst_decouple (.*);
  reg_l #(DW) o_data_r (
      .load(i_fire),
      .din (i_data),
      .dout(o_data),
      .*
  );

endmodule



`define FIFO_DIFFTEST

`ifdef FIFO_DIFFTEST
import "DPI-C" function int fifo_create(
  int id,
  string name
);
import "DPI-C" function void fifo_push(
  int id,
  longint value
);
import "DPI-C" function void fifo_pop(
  int id,
  longint value
);
`endif

module fifo #(
    parameter DW = 32,
    parameter AW = 4
) (

    input clk,
    input rst,

    input  flush,
    output full,
    output empty,

    input i_valid,
    input [DW-1:0] i_data,
    output i_ready,
    output o_valid,
    output [DW-1:0] o_data,
    input o_ready
);

  logic [AW:0] wptr, rptr;

  wire [AW:0] rptr_next;
  wire [AW-1:0] waddr = wptr[AW-1:0];
  wire [DW-1:0] wdata = i_data;
  wire [AW-1:0] raddr = o_fire ? rptr_next[AW-1:0] : rptr[AW-1:0];
  wire [DW-1:0] rdata;

  ram_1r_1w #(
      .DW(DW),
      .AW(AW)
  ) buffer (
      .cs(1'b1),
      .we(i_fire),
      .re(1'b1),
      .*
  );

  wire i_fire = i_ready & i_valid;
  wire o_fire = o_ready & o_valid;

  wire wptr_load = i_fire;
  wire [AW:0] wptr_next = wptr + 'b1;
  reg_lr #((AW + 1), {(AW + 1) {1'b0}}) wptr_reg (
      .clk,
      .rst (rst | flush),
      .load(wptr_load),
      .din (wptr_next),
      .dout(wptr)
  );

  wire rptr_load = o_fire;
  assign rptr_next = rptr + 'b1;
  reg_lr #((AW + 1), {(AW + 1) {1'b0}}) rptr_reg (
      .clk,
      .rst (rst | flush),
      .load(rptr_load),
      .din (rptr_next),
      .dout(rptr)
  );

  logic valid_r;
  wire valid_next = o_fire ? (rptr_next != wptr) : (rptr != wptr);
  reg_r #(1, 1'b0) valid_reg (
      .clk,
      .rst (rst | flush),
      .din (valid_next),
      .dout(valid_r)
  );

  assign empty = wptr == rptr;
  assign full = (wptr[AW-1:0] == rptr[AW-1:0]) & (wptr[AW] != rptr[AW]);

  assign i_ready = ~full;
  assign o_valid = valid_r;
  assign o_data = rdata;

`ifdef FIFO_DIFFTEST
  generate
    if (DW <= 64) begin : fifo_diff
      int fifo_id = 0;
      logic [63:0] push_value, pop_value;
      always_comb begin
        push_value = 'b0;
        push_value[DW-1:0] = i_data;
        pop_value = 'b0;
        pop_value[DW-1:0] = o_data;
      end
      always_ff @(posedge clk) begin
        if (rst) begin
          fifo_id <= fifo_create(fifo_id, $sformatf("%m"));
        end else begin
          if (o_fire) fifo_pop(fifo_id, pop_value);
          if (i_fire) fifo_push(fifo_id, push_value);
        end
      end
    end else begin : DW_BIG
      initial begin
        $display("DW %d >= 64", DW);
      end
    end
  endgenerate
`endif
endmodule



module multi_fifo #(
    parameter DW = 32,
    parameter AW = 4,
    parameter PW = 2,
    localparam BANK_NUM = 2 ** PW
) (
    input clk,
    input rst,

    input  flush,
    output almost_full,
    output empty,

    input i_valid,
    input [DW-1:0] i_data[BANK_NUM-1:0],
    input i_data_valid[BANK_NUM-1:0],
    output i_ready,

    output o_valid,
    output [DW-1:0] o_data[BANK_NUM-1:0],
    output o_data_valid[BANK_NUM-1:0],
    input o_data_ready[BANK_NUM-1:0],
    input o_ready
);


  wire i_fire = i_ready & i_valid;
  wire o_fire = o_ready & o_valid;
  wire i_data_fire[BANK_NUM-1:0];
  wire o_data_fire[BANK_NUM-1:0];

`ifdef FIFO_DIFFTEST
  generate
    int fifo_id = 0;
    logic [63:0] push_value[BANK_NUM-1:0], pop_value[BANK_NUM-1:0];
    integer fifo_diff_i;

    if (DW <= 64) begin : fifo_diff
      always_comb begin
        for (fifo_diff_i = 0; fifo_diff_i < BANK_NUM; fifo_diff_i = fifo_diff_i + 1) begin
          push_value[fifo_diff_i] = 'b0;
          push_value[fifo_diff_i][DW-1:0] = i_data[fifo_diff_i];
          pop_value[fifo_diff_i] = 'b0;
          pop_value[fifo_diff_i][DW-1:0] = o_data[fifo_diff_i];
        end
      end
    end else begin : DW_BIG
      initial begin
        $display("DW %d >= 64", DW);
      end
      always_comb begin
        for (fifo_diff_i = 0; fifo_diff_i < BANK_NUM; fifo_diff_i = fifo_diff_i + 1) begin
          push_value[fifo_diff_i] = 'b0;
          push_value[fifo_diff_i] = {(^i_data[fifo_diff_i][DW-1:63]), i_data[fifo_diff_i][62:0]};
          pop_value[fifo_diff_i]  = 'b0;
          pop_value[fifo_diff_i]  = {(^o_data[fifo_diff_i][DW-1:63]), o_data[fifo_diff_i][62:0]};
        end
      end
    end
    integer fifo_diff_j;
    always_ff @(posedge clk) begin
      if (rst | flush) begin
        fifo_id <= fifo_create(fifo_id, $sformatf("%m"));
      end else begin
        if (o_fire) begin
          for (fifo_diff_j = 0; fifo_diff_j < BANK_NUM; fifo_diff_j = fifo_diff_j + 1) begin
            if (o_data_fire[fifo_diff_j]) fifo_pop(fifo_id, pop_value[fifo_diff_j]);
          end
        end
        if (i_fire) begin
          for (fifo_diff_j = 0; fifo_diff_j < BANK_NUM; fifo_diff_j = fifo_diff_j + 1) begin
            if (i_data_fire[fifo_diff_j]) fifo_push(fifo_id, push_value[fifo_diff_j]);
          end
        end
      end
    end
  endgenerate
`endif

  logic [AW:0] wptr, rptr, wptr_next, rptr_next;
  wire [AW:0] ram_rptr = o_fire ? rptr_next : rptr;

  logic [AW:0] data_num, data_num_next, data_num_in, data_num_out;

  integer i;
  always_comb begin
    data_num_in  = 'b0;
    data_num_out = 'b0;
    for (i = 0; i < BANK_NUM; i = i + 1) begin
      data_num_in  = data_num_in + {{AW{1'b0}}, i_data_fire[i]};
      data_num_out = data_num_out + {{AW{1'b0}}, o_data_fire[i]};
    end
    wptr_next = wptr + data_num_in;
    rptr_next = rptr + data_num_out;
    data_num_next = data_num + (i_fire ? data_num_in : 'b0) - (o_fire ? data_num_out : 'b0);
  end

  assign empty = data_num == 'b0;
  wire [AW:0] data_num_next_max = data_num + BANK_NUM;
  assign almost_full = data_num_next_max[AW];
  assign i_ready = ~almost_full;



  reg_r #((AW + 1), {(AW + 1) {1'b0}}) data_num_r (
      .clk,
      .rst (rst | flush),
      .din (data_num_next),
      .dout(data_num)
  );
  reg_lr #((AW + 1), {(AW + 1) {1'b0}}) wptr_r (
      .clk,
      .rst (rst | flush),
      .load(i_fire),
      .din (wptr_next),
      .dout(wptr)
  );

  reg_lr #((AW + 1), {(AW + 1) {1'b0}}) rptr_r (
      .clk,
      .rst (rst | flush),
      .load(o_fire),
      .din (rptr_next),
      .dout(rptr)
  );


  genvar gi;
  generate
    for (gi = 0; gi < BANK_NUM; gi = gi + 1) begin : data_fire
      assign i_data_fire[gi] = i_data_valid[gi] && i_fire;
      assign o_data_fire[gi] = o_data_valid[gi] && o_data_ready[gi];
    end

    wire [PW-1:0] w_offset = wptr[PW-1:0];

    wire [PW-1:0] r_offset = ram_rptr[PW-1:0];
    wire [AW:0] ready_num_next = wptr - ram_rptr;
    logic [AW:0] ready_num;
    reg_r #((AW + 1), {(AW + 1) {1'b0}}) ready_num_r (
        .clk,
        .rst (rst | flush),
        .din (ready_num_next),
        .dout(ready_num)
    );
    reg_r #(1, 1'b0) valid_reg (
        .clk,
        .rst (rst | flush),
        .din (ready_num_next != 'b0),
        .dout(o_valid)
    );

    logic [DW-1:0] r_data_vec[BANK_NUM-1:0];
    for (gi = 0; gi < BANK_NUM; gi = gi + 1) begin : bank
      logic [DW-1:0] wdata, rdata;
      logic we;
      logic [AW-1:PW] waddr, raddr;

      wire [PW:0] ra_index = r_offset + (BANK_NUM - 1 - gi);
      wire [PW:0] wa_index = w_offset + (BANK_NUM - 1 - gi);
//      wire [PW-1:0] rd_index = gi - r_offset;
      wire [PW-1:0] rd_index = gi + r_offset;
      wire [PW-1:0] wd_index = gi - w_offset;

      logic [PW-1:0] rd_index_r;
      reg_r #(PW, {PW{1'b0}}) rd_index_r_r (
          .clk,
          .rst (rst | flush),
          .din (rd_index),
          .dout(rd_index_r)
      );
      assign waddr = wptr[AW-1:PW] + {{(AW - PW - 1) {1'b0}}, wa_index[PW]};
      assign raddr = ram_rptr[AW-1:PW] + {{(AW - PW - 1) {1'b0}}, ra_index[PW]};
      assign we = i_data_fire[wd_index[PW-1:0]];
      assign wdata = i_data[wd_index[PW-1:0]];
//      gi <> gi + r_offset
      assign r_data_vec[gi] = rdata;
      assign o_data[gi] = r_data_vec[rd_index_r];
      ram_1r_1w #(DW, (AW - PW)) bufffer (
          .cs(1'b1),
          .re(1'b1),
          .*
      );
      assign o_data_valid[gi] = gi < ready_num ? 1'b1 : 1'b0;
    end
  endgenerate

endmodule

module multi_rw_fifo #(
    parameter DW = 32,
    parameter AW = 4,
    parameter READ_PORTS = 3,
    parameter WRITE_PORTS = 5,
    localparam MAX_PORTS = READ_PORTS > WRITE_PORTS ? READ_PORTS : WRITE_PORTS,
    localparam BANK_AW = $clog2(MAX_PORTS),
    localparam BANK_NUM = 2 ** BANK_AW
) (
    input clk,
    input rst,

    input  flush,
    output almost_full,
    output empty,

    input i_valid,
    input [DW-1:0] i_data[WRITE_PORTS-1:0],
    input i_data_valid[WRITE_PORTS-1:0],
    output i_ready,

    output o_valid,
    output [DW-1:0] o_data[READ_PORTS-1:0],
    output o_data_valid[READ_PORTS-1:0],
    input o_data_ready[READ_PORTS-1:0],
    input o_ready
);
  genvar gi;
  generate
    logic [DW-1:0] multi_fifo_i_data[BANK_NUM-1:0];
    logic multi_fifo_i_data_valid[BANK_NUM-1:0];
    logic [DW-1:0] multi_fifo_o_data[BANK_NUM-1:0];
    logic multi_fifo_o_data_valid[BANK_NUM-1:0];
    logic multi_fifo_o_data_ready[BANK_NUM-1:0];
    for (gi = 0; gi < BANK_NUM; gi = gi + 1) begin : BankPorts
      if (gi < WRITE_PORTS) begin : BankWritePorts
        assign multi_fifo_i_data[gi] = i_data[gi];
        assign multi_fifo_i_data_valid[gi] = i_data_valid[gi];
      end else begin : UnusedBankWritePorts
        assign multi_fifo_i_data[gi] = 'b0;
        assign multi_fifo_i_data_valid[gi] = 'b0;
      end

      if (gi < READ_PORTS) begin : BankReadPorts
        assign o_data[gi] = multi_fifo_o_data[gi];
        assign o_data_valid[gi] = multi_fifo_o_data_valid[gi];
        assign multi_fifo_o_data_ready[gi] = o_data_ready[gi];
      end else begin : UnusedBankReadPorts
        assign multi_fifo_o_data_ready[gi] = 'b0;
      end
    end

    multi_fifo #(DW, AW, BANK_AW) inst_multi_fifo (
        .i_data(multi_fifo_i_data),
        .i_data_valid(multi_fifo_i_data_valid),
        .o_data(multi_fifo_o_data),
        .o_data_valid(multi_fifo_o_data_valid),
        .o_data_ready(multi_fifo_o_data_ready),
        .*
    );

  endgenerate
endmodule

